package com.fyz.aaronfjava.util;


import org.apache.tomcat.util.codec.binary.Base64;
import javax.crypto.Cipher;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import java.util.Properties;
/**
 * @Author：AaronF
 * @Date：2024/1/25 13:50
 * @description RSA密钥生成工具类
 */
public class RSAUtil {

	/**
	 * 用于生成公钥和私钥对
	 */
	private static final KeyPairGenerator keyPairGen;
	/**
	 * 公钥
	 */
	private static RSAPublicKey publicKey;
	/**
	 * 私钥
	 */
	private static RSAPrivateKey privateKey;
	/**
	 * 公钥文件路径
	 */
	private static final String publicKeyPath;
	/**
	 * 私钥文件路径
	 */
	private static final String privateKeyPath;

	static {
		try {
			//使用RSA对称加密
			keyPairGen = KeyPairGenerator.getInstance("RSA");
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException(e);
		}
		//这里填写自己的properties文件路径
		Properties properties = IoUtil.getPropertiesByRelative("RSA.properties");
		publicKeyPath = properties.getProperty("publicKeyPath");
		privateKeyPath = properties.getProperty("privateKeyPath");
	}

	/**
	 * 第一次使用工具类的时候，调用该方法生成一次密钥对
	 */
	public static void main(String[] args) {
		generateKeyPair(1024,publicKeyPath,privateKeyPath);
	}

	/**
	 * 生成密钥对 写入指定文件
	 * @param keysize 这里指定的是密钥对的模数的长度 设置为1024以上（安全起见）
	 *                注意指定的是（二进制）位数 比如指定为1024 那么得到的模数换算成二进制是1024位
	 */
	private static void generateKeyPair(int keysize, String publicKeyPath,String privateKeyPath){
		if (keysize < 1024){
			System.out.println("生成失败，模数长度要求1024以上");
			return;
		}
		// 这里指定的是密钥对的模数 单位是bit 内部会随机生成
		keyPairGen.initialize(keysize);
		// 生成一个密钥对
		KeyPair keyPair = keyPairGen.generateKeyPair();
		PublicKey publicKey =  keyPair.getPublic();
		PrivateKey privateKey = keyPair.getPrivate();
		// 将公钥和私钥保存到指定文件 并使用base64编码
		IoUtil.write(publicKeyPath,Base64.encodeBase64(publicKey.getEncoded()),false);
		IoUtil.write(privateKeyPath,Base64.encodeBase64(privateKey.getEncoded()),false);
		System.out.println("生成密钥对成功");
	}

	/**
	 * 通过私钥对字符串进行加密
	 * @param str
	 * @return 输出base64编码的加密字符串
	 */
	public static String encryptStrByPrivate(String str){
		byte[] encrypt = encryptByPrivate(str.getBytes(StandardCharsets.UTF_8));
		return new String(Base64.encodeBase64(encrypt),StandardCharsets.UTF_8);
	}

	/**
	 * 通过私钥对字符串进行加密
	 * @param input
	 * @return 输出加密后的字节数组
	 */
	public static byte[] encryptByPrivate(byte[] input){
		if (privateKey == null){
			//生成私钥
			generatePrivateKey();
		}
		return encrypt(input,privateKey);
	}

	/**
	 * 通过公钥对字符串进行加密
	 * @param str
	 * @return 输出base64编码的加密字符串
	 */
	public static String encryptStrByPublic(String str){
		byte[] encrypt = encryptByPublic(str.getBytes(StandardCharsets.UTF_8));
		return new String(Base64.encodeBase64(encrypt),StandardCharsets.UTF_8);
	}

	/**
	 * 通过公钥对字符串进行加密
	 * @param input
	 * @return 输出加密后的字节数组
	 */
	public static byte[] encryptByPublic(byte[] input){
		if (publicKey == null){
			//生成公钥
			generatePublicKey();
		}
		return encrypt(input,publicKey);
	}

	/**
	 * 对字节数组进行加密
	 * @param input
	 * @return 输出加密后的字节数组
	 * @throws Exception
	 */
    private static byte[] encrypt(byte[] input,Key key){
		try {
			//RSA加密
			Cipher cipher = Cipher.getInstance("RSA");
			cipher.init(Cipher.ENCRYPT_MODE, key);
			return cipher.doFinal(input);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
    }

	/**
	 * 通过私钥对字符串进行解密
	 * @param str base64编码的加密字符串
	 * @return 输出解密后的字符串
	 */
	public static String decryptStrByPrivate(String str){
		byte[] decrypt = decryptByPrivate(Base64.decodeBase64(str.getBytes(StandardCharsets.UTF_8)));
		return new String(decrypt,StandardCharsets.UTF_8);
	}

	/**
	 * 通过私钥进行解密
	 * @param input
	 * @return 输出解密后的字符串
	 */
	public static byte[] decryptByPrivate(byte[] input){
		if (privateKey == null){
			//生成私钥
			generatePrivateKey();
		}
		return decrypt(input,privateKey);
    }

	/**
	 * 通过公钥对字符串进行解密
	 * @param str base64编码的加密字符串
	 * @return 输出解密后的字符串
	 */
	public static String decryptStrByPublic(String str){
		byte[] decrypt = decryptByPublic(Base64.decodeBase64(str.getBytes(StandardCharsets.UTF_8)));
		return new String(decrypt,StandardCharsets.UTF_8);
	}

	/**
	 * 通过公钥进行解密
	 * @param input
	 * @return 输出解密后的字符串
	 */
	public static byte[] decryptByPublic(byte[] input){
		if (publicKey == null){
			//生成公钥
			generatePublicKey();
		}
		return decrypt(input,publicKey);
	}

	private static byte[] decrypt(byte[] input,Key key){
		try {
			//RSA解密
			Cipher cipher = Cipher.getInstance("RSA");
			cipher.init(Cipher.DECRYPT_MODE, key);
			return cipher.doFinal(input);
		}catch (Exception e){
			throw new RuntimeException(e);
		}
	}

	/**
	 * 生成公钥
	 */
	private static void generatePublicKey(){
		if (publicKey == null && StringUtil.isNotBlank(publicKeyPath)){
			synchronized (publicKeyPath){
				//检测是否已经生成
				if (publicKey != null){
					return;
				}
				//读取公钥
				String publicKeyStr = IoUtil.readAsString(publicKeyPath);
				//base64解码的公钥
				byte[] decoded = Base64.decodeBase64(publicKeyStr);
				try {
					//生成公钥对象
					publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
				} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
					publicKey = null;
					throw new RuntimeException(e);
				}
			}
		}
    }

	/**
	 * 生成私钥
	 */
	private static void generatePrivateKey(){
		if (privateKey == null && StringUtil.isNotBlank(privateKeyPath)){
			synchronized (privateKeyPath){
				//检测是否已经生成
				if (privateKey != null){
					return;
				}
				//读取私钥
				String privateKeyStr = IoUtil.readAsString(privateKeyPath);
				//base64解码的私钥
				byte[] decoded = Base64.decodeBase64(privateKeyStr);
				try {
					//生成私钥对象
					privateKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
				} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
					privateKey = null;
					throw new RuntimeException(e);
				}
			}
		}
	}
}

